﻿//
//  Monkey Manager
//  Copyright (C) 2012 Victor Tereschenko
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//     
// ========================================================================

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Xml;

using ManagerDB;
using ManagerDB.Options;

using Sibvic;

namespace AddManager
{
    public partial class AddWindow : Form
    {
        AddController mAddController;
        HashCalculator mHashCalculator;
        DropboxSyncController mBackuper;
        ManagerOptions mOptions;
        IMutableDatabase mDB;
        List<AddingFileInfo> mFiles = new List<AddingFileInfo>();
        AutoHasher mAutoHasher;

        public AddWindow()
        {
            InitializeComponent();

            string fileName = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "database.xml");
            mOptions = OptionsFactory.ReadFromFile(fileName);
            if (mOptions == null)
                mOptions = OptionsFactory.Create();

            mBackuper = new DropboxSyncController(mOptions.DBFile, mOptions.BackupFile);
            mBackuper.UpdateOriginal();
        }

        private void AddWindow_Load(object sender, EventArgs e)
        {
            try
            {
                mDB = DatabaseFactory.CreateMutable(mOptions);
                mHashCalculator = new HashCalculator();
                mHashCalculator.FileHashing += new EventHandler<FileHashingEventArgs>(onFileHashing);
                mHashCalculator.FileHashed += new EventHandler<FileHashedEventArgs>(onFileHashed);

                mAddController = new AddController(mDB);
                mAddController.FileAdding += new EventHandler<FileAddingEventArgs>(onFileAdding);
                mAddController.FileAdded += new EventHandler<FileAddedEventArgs>(onFileAdded);
                mAddController.FileAddError += new EventHandler<FileAddErrorEventArgs>(onFileAddError);

                Text = mOptions.DBName;

                mAutoHasher = new AutoHasher(mDB, mHashCalculator);
                mAutoHasher.DuplicateFound += new EventHandler<DuplicateFoundEventArgs>(onDuplicateFound);
                mAutoHasher.MissingFound += new EventHandler<MissingFoundEventArgs>(onMissingFound);
            }
            catch (NotSupportedDatabse ex)
            {
                System.Windows.Forms.MessageBox.Show(ex.Message);
                Close();
            }
        }

        class AddingFileInfo : FileHashInfo
        {
            public enum Status
            {
                None,
                Hashing,
                Hashed,
                Adding,
                Added,
                Error
            }

            public Status FileStatus = Status.None;
            public bool Move = false;
            public string Message;
        }

        /// <summary>
        /// Called when duplicate file found in DB. Called from seperate thread.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void onDuplicateFound(object sender, DuplicateFoundEventArgs e)
        {
            var item = new AddingFileInfo();
            item.FileStatus = AddingFileInfo.Status.Error;
            item.Message = "Duplicate file: " + e.Path;
            filesListView.Invoke((Action)(() =>
            {
                mFiles.Add(item);
                mCache = null;
                filesListView.VirtualListSize = mFiles.Count;
            }));            
        }

        /// <summary>
        /// Called when missing file found in DB. Called from seperate thread.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void onMissingFound(object sender, MissingFoundEventArgs e)
        {
            var item = new AddingFileInfo();
            item.FileStatus = AddingFileInfo.Status.Error;
            item.Message = string.Format("Missing file: {0} ({1})", e.Path, e.File.Title);
            filesListView.Invoke((Action)(() =>
            {
                mFiles.Add(item);
                mCache = null;
                filesListView.VirtualListSize = mFiles.Count;
            }));
        }

        /// <summary>
        /// Called before file added to the db. Called from seperate thread.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void onFileAdding(object sender, FileAddingEventArgs e)
        {
            var fileInfo = findFile(e.File.FileInfo.FullName);
            if (fileInfo == null)
            {
                filesListView.Invoke((Action)(() =>
                {
                    statusLabel.Text = "Adding " + e.File.FileInfo.Name;
                }));
                return;
            }
            fileInfo.FileStatus = AddingFileInfo.Status.Adding;
            filesListView.Invoke((Action)(() =>
            {
                statusLabel.Text = "Adding " + fileInfo.FileInfo.Name;
                mCache = null;
                filesListView.Invalidate();
            }));
        }

        /// <summary>
        /// Called after file has added to the db. Called from seperate thread.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void onFileAdded(object sender, FileAddedEventArgs e)
        {
            var fileInfo = findFile(e.File.FileInfo.FullName);
            if (fileInfo == null)
            {
                filesListView.Invoke((Action)(() =>
                {
                    statusLabel.Text = e.File.FileInfo.Name + " added!";
                }));
                return;
            }
            fileInfo.FileStatus = AddingFileInfo.Status.Added;
            filesListView.Invoke((Action)(() =>
            {
                statusLabel.Text = fileInfo.FileInfo.Name + " added!";
                mCache = null;
                mFiles.Remove(fileInfo);
                filesListView.VirtualListSize = mFiles.Count;
            }));
        }

        /// <summary>
        /// Called after file has failed to add to the db. Called from seperate thread.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void onFileAddError(object sender, FileAddErrorEventArgs e)
        {
            var fileInfo = findFile(e.File.FileInfo.FullName);
            if (fileInfo == null)
            {
                filesListView.Invoke((Action)(() =>
                {
                    statusLabel.Text = "Error while adding " + e.File.FileInfo.Name;
                }));
                return;
            }
            fileInfo.FileStatus = AddingFileInfo.Status.Error;
            filesListView.Invoke((Action)(() =>
            {
                statusLabel.Text = "Error while adding " + fileInfo.FileInfo.Name;
                mCache = null;
                filesListView.Invalidate();
            }));
        }

        /// <summary>
        /// Called before file hashing start.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void onFileHashing(object sender, FileHashingEventArgs e)
        {
            var fileInfo = findFile(e.FileName);
            if (fileInfo == null)
            {
                filesListView.Invoke((Action)(() =>
                {
                    statusLabel.Text = "Hashing " + e.FileName;
                }));
                return;
            }
            fileInfo.FileStatus = AddingFileInfo.Status.Hashing;
            filesListView.Invoke((Action)(() =>
            {
                statusLabel.Text = "Hashing " + fileInfo.FileInfo.Name;
                mCache = null;
                filesListView.Invalidate();
            }));
        }

        /// <summary>
        /// Called when file has hashed. Called from seperate thread
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void onFileHashed(object sender, FileHashedEventArgs e)
        {
            var fileInfo = findFile(e.FileName);
            if (fileInfo == null)
            {
                filesListView.Invoke((Action)(() =>
                {
                    statusLabel.Text = e.FileName + " hashed";
                }));
                return;
            }
            fileInfo.TTH = e.TTH;
            fileInfo.FileStatus = AddingFileInfo.Status.Hashed;
            mAddController.Add(fileInfo, fileInfo.Move);
            filesListView.Invoke((Action)(() =>
            {
                statusLabel.Text = fileInfo.FileInfo.Name + " hashed";
                mCache = null;
                filesListView.Invalidate();
            }));
        }

        /// <summary>
        /// Finds file in list by file name.
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        AddingFileInfo findFile(string fileName)
        {
            return mFiles.Find(file => file.FileInfo != null && file.FileInfo.FullName == fileName);
        }

        private void addFileButton_Click(object sender, EventArgs e)
        {
            var ofd = new OpenFileDialog();
            ofd.Multiselect = true;
            if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                foreach (string filename in ofd.FileNames)
                {
                    var item = new AddingFileInfo();
                    item.FileInfo = new FileInfo(filename);
                    item.Move = moveCheckBox.Checked;
                    mHashCalculator.Add(filename);
                    mFiles.Add(item);
                }
            }
            mCache = null;
            filesListView.VirtualListSize = mFiles.Count;
        }

        private ListViewItem[] mCache;
        private int mFirstItem;

        private void filesListView_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
        {
            if (mCache != null && e.ItemIndex >= mFirstItem && e.ItemIndex < mFirstItem + mCache.Length)
                e.Item = mCache[e.ItemIndex - mFirstItem];
            if (e.Item == null)
                e.Item = createItem(mFiles[e.ItemIndex]);
        }

        private void filesListView_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)
        {
            if (mCache != null && e.StartIndex >= mFirstItem && e.EndIndex <= mFirstItem + mCache.Length)
                return;

            mFirstItem = e.StartIndex;
            int length = e.EndIndex - e.StartIndex + 1;
            mCache = new ListViewItem[length];

            for (int i = 0; i < mCache.Length; i++)
                mCache[i] = createItem(mFiles[i + e.StartIndex]);
        }

        /// <summary>
        /// Creates new list view item for a hash file info
        /// </summary>
        /// <param name="hashInfo"></param>
        /// <returns></returns>
        ListViewItem createItem(AddingFileInfo hashInfo)
        {
            var item = new ListViewItem(hashInfo.FileInfo == null ? hashInfo.Message : System.IO.Path.GetFileName(hashInfo.FileInfo.FullName));
            switch (hashInfo.FileStatus)
            {
                case AddingFileInfo.Status.None:
                case AddingFileInfo.Status.Added:
                    break;
                case AddingFileInfo.Status.Hashing:
                    item.BackColor = Color.Yellow;
                    break;
                case AddingFileInfo.Status.Hashed:
                    item.BackColor = Color.Lime;
                    break;
                case AddingFileInfo.Status.Adding:
                    item.BackColor = Color.LightBlue;
                    break;
                case AddingFileInfo.Status.Error:
                    item.BackColor = Color.Red;
                    break;
            }
                
            var subitem = new ListViewItem.ListViewSubItem();
            subitem.Text = hashInfo.FileInfo == null ? "" : hashInfo.FileInfo.Length.ToString();
            item.SubItems.Add(subitem);

            subitem = new ListViewItem.ListViewSubItem();
            subitem.Text = hashInfo.TTH ?? "";
            item.SubItems.Add(subitem);

            return item;
        }

        private void AddWindow_FormClosed(object sender, FormClosedEventArgs e)
        {
            mHashCalculator.Dispose();
            mAddController.Dispose();
            if (mBackuper != null)
                mBackuper.UpdateDropbox();
        }
    }
}
